// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu, jhkim

#pragma once

#include <memory>
#include <vector>
#include <map>
#include <string>
#include <utility>
#include <unordered_map>

#include "coin/proto/coin_executor.pb.h"
#include "coin/proto/coin_order_enums.pb.h"
#include "coin/proto/coin_telemetry.pb.h"

#include "coin2/exchange/base/log/strategy_logger.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"

#include "coin2/strategy/linear_model/logic.h"
#include "coin2/strategy/linear_model/util.h"
#include "coin2/strategy/order_executor/executor.h"

namespace coin2::strategy::linear_model {

// represents 1 symbol
class LmAggLogic : public LinearModelLogic {
 public:
  explicit LmAggLogic(
      const MarketExchangeApi& mea,
      const IProduct& target_product,
      const Symbol* target_symbol,
      const LmProductConfig& lm_product_config,
      ProductEncyclopedia* product_cache)
      : LinearModelLogic(mea, target_product, target_symbol, product_cache),
        enable_telemetry_(lm_product_config.enable_telemetry()),
        mea_(mea),
        leverage_rate_(lm_product_config.leverage_rate()),
        lm_agg_config_(lm_product_config.agg_config()),
        threshold_bps_(
            lm_agg_config_.threshold_bps()
            + lm_agg_config_.threshold_bps_add()),
        cancel_edge_bps_(
            lm_agg_config_.cancel_edge_bps()
            + lm_agg_config_.cancel_edge_bps_add()),
        posting_edge_bps_add_(lm_agg_config_.posting_edge_bps_add()),
        post_cooltime_(lm_agg_config_.post_cooltime_sec() * 1e9),
        taker_cooltime_(lm_agg_config_.taker_cooltime_sec() * 1e9),
        pred_interval_(lm_agg_config_.prediction_window_sec() * 1e9),
        clear_edge_manager_(
            lm_agg_config_.add_clear_lean_per_window_sec(),
            lm_agg_config_.add_clear_lean_bps()),
        signal_rmse_manager_(
            lm_agg_config_.signal_rmse_horiz_sec() * 1e9,
            lm_agg_config_.signal_rmse_sec() * 1e9,
            100),  // 100 ns sampling interval for midp
        prev_executor_config_ts_(0),
        prev_ask_post_time_(0),
        prev_bid_post_time_(0),
        prev_sell_taker_time_(0),
        prev_buy_taker_time_(0),
        sim_account_id_(lm_product_config.sim_account_id()),
        use_signal_rmse_maker_(lm_agg_config_.signal_rmse_maker_multiplier() > 0.0),
        use_signal_rmse_taker_(lm_agg_config_.signal_rmse_taker_multiplier() > 0.0),
        new_bid_fill_(false),
        prev_bid_fill_price_(0.0),
        prev_bid_fill_ts_(0L),
        new_ask_fill_(false),
        prev_ask_fill_price_(0.0),
        prev_ask_fill_ts_(0L),
        buy_def_edge_add_(0.0),
        sell_def_edge_add_(0.0),
        def_edge_ns_(lm_agg_config_.defensive_edge_sec() * Time::kNanosecondsPerSecond),
        prev_buy_def_edge_restored_ts_(0L),
        prev_sell_def_edge_restored_ts_(0L) {
    SPDLOG_INFO("[LmAggLogic] config json = {}", lm_agg_config_.DebugString());

    // default is 0.65. used only when midp_model = true
    penalize_spread_ratio_ = lm_agg_config_.penalize_spread_ratio();
  }

  void InitImpl(
      ::coin2::app::Driver* driver,
      coin2::exchange::base::strategy_util::StrategyLogger* strat_logger) override {
    auto curr_time = GetCurrentTimestamp();
    for (auto& [name, oe] : driver->strategy()->order_executors(sim_account_id_)) {
      (void)name;
      if (oe->mea() == mea_) {
        lholder_.InitExecutor(oe.get(), strat_logger, enable_telemetry_, curr_time);
      }
    }

    LoadPreRebootRMSEValues(strat_logger);
  }

  void LoadPreRebootRMSEValues(coin2::exchange::base::strategy_util::StrategyLogger* strat_logger) {
    auto curr_time = GetCurrentTimestamp();
    signal_rmse_manager_.SetStratLogger(strat_logger);
    std::string log_root = strat_logger->GetLogRoot();
    std::string machine = strat_logger->GetMachine();
    coin::proto::StrategyRequestProto strat_request = strat_logger->GetStrategyRequestProto();
    const size_t BUF_SIZE = 1000 * 1024;

    coin2::exchange::base::strategy_util::StrategyLogReader reader(
        strat_request, log_root, machine,
        curr_time - 300 * Time::kNanosecondsPerSecond, curr_time, BUF_SIZE);

    coin::proto::StrategyStoredValue ssv_loaded[SignalRMSEManager::stored_value_type::N_TYPES];

    coin::proto::StrategyLog strat_log_proto;
    std::string prod_name = lholder_.target_product.relative_norm();
    bool data_okay = true;

    if (lm_agg_config_.signal_rmse_use_pre_reboot_values()) {
      while (reader.Read(&strat_log_proto)) {
        if (strat_log_proto.type() != coin::proto::StrategyLog::STRAT_STORED_VALUE) {
          continue;
        }

        if (strat_log_proto.strat_stored_value().product_name() != prod_name) {
          continue;
        }

        int value_type = strat_log_proto.strat_stored_value().value_type();
        if (value_type < SignalRMSEManager::stored_value_type::N_TYPES) {
          ssv_loaded[value_type] = strat_log_proto.strat_stored_value();
        } else {
          data_okay = false;
          LOG(INFO) << "pre-reboot data has invalid values:\n"
              << strat_log_proto.DebugString();
          break;
        }
      }

      if (!data_okay) {
        for (int i = 0; i < SignalRMSEManager::stored_value_type::N_TYPES; i++) {
          ssv_loaded[i].set_timestamp_begin(0L);
          ssv_loaded[i].set_timestamp_end(0L);
        }
      }

      for (int i = 0; i < SignalRMSEManager::stored_value_type::N_TYPES; i++) {
        LOG(INFO) << "tried loading pre-reboot RMSE values for type " << i << ":\n"
            << ssv_loaded[i].DebugString();
      }
    } else {
      for (int i = 0; i < SignalRMSEManager::stored_value_type::N_TYPES; i++) {
        ssv_loaded[i].set_timestamp_begin(0L);
        ssv_loaded[i].set_timestamp_end(0L);
      }
    }

    signal_rmse_manager_.SetPreRebootValues(curr_time, prod_name, ssv_loaded);
  }

  void OnFill(
      int64_t timestamp,
      coin::proto::TradeSide trade_side,
      double fill_price,
      double fill_qty) final {}

  void UpdateMidp(
      StrategyReporter2* strat_reporter,
      const IProduct* target_product_given) final {
    strat_reporter->UpdateMidp(*target_product_given, GetMidPrice());
    lholder_.UpdateMidp(strat_reporter, target_product_given);
  }

  void onAccountInfo(const coin2::exchange::base::order::OrderUpdate& upd) final {
    if (upd.sim_account_id && sim_account_id_ != *upd.sim_account_id) {
      SPDLOG_INFO_EVERY_NS(
          200'000'000'000L,
          upd.timestamp(),
          "onAccountInfo: sim_account_id mismatch");
      return;
    }
    if (!upd.is_account_info()) {
      SPDLOG_INFO_EVERY_NS(
          200'000'000'000L,
          upd.timestamp(),
          "onAccountInfo: is_account_info != 1");
      return;
    }
    if (!lholder_.product_holder) {
      SPDLOG_INFO_EVERY_NS(
          200'000'000'000L,
          upd.timestamp(),
          "onAccountInfo: product_holder is null");
      return;
    }
    lholder_.oe->onAccountInfo(*lholder_.product_holder, upd);
  }

  void onAccountOrder(const coin2::exchange::base::order::OrderUpdate& upd) final {
    if (upd.sim_account_id && sim_account_id_ != *upd.sim_account_id) return;
    if (lholder_.product_holder) lholder_.oe->onAccountOrder(*lholder_.product_holder, upd);

    if (!lholder_.product_holder) {
      LOG(INFO) << "Product holder not initialized yet";
      return;
    }

    auto& event = upd.product_order_info().event();
    int64_t id = event.proc_order_id();
    std::string tag = lholder_.oe->oq(*lholder_.product_holder)->
        order_manager().FindByProcOrderId(id)->order_spec().tag;
    if (event.type() == coin::proto::OrderEvent::ORDER_FILLED &&
        tag == "passive_post") {
      coin::proto::TradeSide trade_side = event.trade_side();
      if (trade_side == coin::proto::TRADE_BUY_SIDE) {
        new_bid_fill_ = true;
        prev_bid_fill_price_ = event.fill_price();
        prev_bid_fill_ts_ = event.event_time();
      } else if (trade_side == coin::proto::TRADE_SELL_SIDE) {
        new_ask_fill_ = true;
        prev_ask_fill_price_ = event.fill_price();
        prev_ask_fill_ts_ = event.event_time();
      }
    }
  }

  void onFeed(
      const Symbol* symbol,
      const FeedUpdate& upd,
      const ::fastfeature::FeedMessage& feed_msg,
      LinearModel* linear_model,
      int64_t* lm_feed_id,
      const int64_t* global_feed_id,
      LinearModelDelegateProto* delegate,
      bool simulation) override;

  void TryTrigger(
      const Symbol* symbol,
      const FeedUpdate& upd,
      const ::fastfeature::FeedMessage& feed_msg,
      LinearModel* linear_model,
      int64_t* lm_feed_id,
      const int64_t* global_feed_id,
      LinearModelDelegateProto* delegate,
      int64_t timestamp_override,
      std::optional<double>* midp_return);

 private:
  const bool enable_telemetry_;
  const MarketExchangeApi mea_;
  const double leverage_rate_;

  /* -------------------------------------------------------------------------- */
  /*                               strategy config                              */
  /* -------------------------------------------------------------------------- */

  LmAggLogicConfig lm_agg_config_;
  const double threshold_bps_;
  const double cancel_edge_bps_;
  const double posting_edge_bps_add_;
  const int64_t post_cooltime_;
  const int64_t taker_cooltime_;
  PredictionInterval pred_interval_;
  ClearEdgeManager clear_edge_manager_;
  SignalRMSEManager signal_rmse_manager_;

  int64_t prev_executor_config_ts_;
  double penalize_spread_ratio_;
  int64_t prev_ask_post_time_;
  int64_t prev_bid_post_time_;
  int64_t prev_sell_taker_time_;
  int64_t prev_buy_taker_time_;
  int64_t sim_account_id_;

  bool use_signal_rmse_maker_;
  bool use_signal_rmse_taker_;
  double signal_rmses_[SignalRMSEManager::stored_value_type::N_TYPES] = {0.0};
  bool signal_rmse_ready_ = false;

  bool new_bid_fill_;
  double prev_bid_fill_price_;
  int64_t prev_bid_fill_ts_;

  bool new_ask_fill_;
  double prev_ask_fill_price_;
  int64_t prev_ask_fill_ts_;

  double buy_def_edge_add_;
  double sell_def_edge_add_;
  int64_t def_edge_ns_;
  int64_t prev_buy_def_edge_restored_ts_;
  int64_t prev_sell_def_edge_restored_ts_;
};

}  // namespace coin2::strategy::linear_model
